#include "Graphics.h"

#ifdef _DEBUG
	#ifndef HR

	#define HR(x)						\
		{								\
			HRESULT hr = x;				\
			if (FAILED(hr))				\
			{							\
				DXTRACE_ERR(#x, hr);	\
				DebugBreak();			\
			}							\
		}
	#endif
	#else
		#ifndef HR
		#define HR(x) x;
	#endif
#endif


Graphics::Graphics()
	: m_pD3DDevice(NULL)
	, m_hWnd(NULL)
	, m_bWindowed(true)
	, m_bInitialized(false)
	, m_pCubeMesh(NULL)
{
	for(int i = 0; i < g_numTextures; i++)
	{
		m_pTextureLibrary[i] = NULL;
		m_cszTextureFileName[i] = "NOTSET";
	}
}

bool Graphics::Init(HWND hWnd)
{
	if(!SetupDevice(hWnd))
		return false;
	if(!SetupCamera())
		return false;
	if(!SetupFont())
		return false;
	if(!SetupMeshes())
		return false;
	if(!SetupLight())
		return false;
	if(!SetupMaterials())
		return false;

	// setup sprite
	D3DXCreateSprite(m_pD3DDevice, &m_pD3DSprite);
	D3DXCreateTextureFromFile(m_pD3DDevice, m_cszTextureFileName[3], &m_pButtonTexture);

	m_bInitialized = true;
	return true;
}

// use a D3DObject to create the device we will use from here on out
bool Graphics::SetupDevice(HWND hWnd)
{
	IDirect3D9* pD3DObject = NULL;
	pD3DObject = Direct3DCreate9(D3D_SDK_VERSION);		// create the d3dobject

	m_hWnd = hWnd;										// make a copy of hWnd for the Graphics class

	RECT rClientRect;
	if(!::GetClientRect(m_hWnd, &rClientRect))
	{
		throw "GetClientRect() in Graphics::SetupDevice() failed";
		return false;
	}

	// grab screen dimensions from the window
	m_dimScreen.width = (int)(rClientRect.right - rClientRect.left);
	m_dimScreen.height = (int)(rClientRect.bottom - rClientRect.top);

	D3DCAPS9 D3DCaps;

	HR(pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));

	DWORD dwBehaviorFlags = 0;	

	// determine what type of vertex processing to use
	if(D3DCaps.VertexProcessingCaps != 0)
		dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	//setup pp
	m_D3Dpp.hDeviceWindow			= m_hWnd;							// what window the device will use
	m_D3Dpp.Windowed				= m_bWindowed;						// whether or not the window is fullscreen or not
	m_D3Dpp.EnableAutoDepthStencil  = TRUE;								// tells the device to manage depth buffering
	m_D3Dpp.AutoDepthStencilFormat	= D3DFMT_D24S8;						// the type of depth buffering
	m_D3Dpp.BackBufferCount			= 1;								// no need for more than one back buffer
	m_D3Dpp.BackBufferFormat		= D3DFMT_X8R8G8B8;
	m_D3Dpp.BackBufferWidth			= m_dimScreen.width;				// the dimensions should match the front screen (front buffer)
	m_D3Dpp.BackBufferHeight		= m_dimScreen.height;
	m_D3Dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;			// after displaying a frame, delete it
	m_D3Dpp.Flags					= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	m_D3Dpp.MultiSampleQuality		= 0;
	m_D3Dpp.MultiSampleType			= D3DMULTISAMPLE_NONE;

	// now create a device using all the above settings for us to use
	pD3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, dwBehaviorFlags, &m_D3Dpp, &m_pD3DDevice);
	pD3DObject->Release();		// we don't need the object after this, release it from memory

	if(m_pD3DDevice == NULL)	// if the device was not set, return false
		return false;

	return true;
}

bool Graphics::SetupCamera()
{
	m_Camera.eyePos = D3DXVECTOR3(0, 0, 0);
	m_Camera.lookAt = D3DXVECTOR3(0, 0, 0);
	m_Camera.upVec =  D3DXVECTOR3(0, 1, 0);

	D3DXMatrixPerspectiveFovLH(&m_matPerspective, D3DXToRadian(65), 
						(float)(m_dimScreen.width / m_dimScreen.height), 1.0f, 1000.0f);
	D3DXMatrixLookAtLH(&m_matView, &m_Camera.eyePos, &m_Camera.lookAt, &m_Camera.upVec);

	m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &m_matPerspective);
	m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_matView);

	return true;
}

bool Graphics::SetupFont()
{
	m_rTextBox.left = 0;
	m_rTextBox.right = 20;
	m_rTextBox.top = 0;
	m_rTextBox.top = 20;

	D3DXCreateFont(m_pD3DDevice, 18, 0, NULL, 0, false,		// initialize the font handler
		DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE, TEXT("Times New Roman"),
		&m_pD3DFont);

	if(NULL == m_pD3DFont)
		return false;

	return true;
}

bool Graphics::SetupMeshes()
{
	// tell the graphics engine about all the meshes we're using
	m_cszCubeMeshFile = "Resource/Meshes/cube.x";
	m_cszBoardMeshFile = "Resource/Meshes/board.x";
	// tell the graphics engine about all the textures we're using
	m_cszTextureFileName[0] = "Resource/Textures/orange.png";
	m_cszTextureFileName[1] = "Resource/Textures/player2.png";
	m_cszTextureFileName[2] = "Resource/Textures/board.png";
	m_cszTextureFileName[3] = "Resource/Textures/button.jpg";

	//load the meshes
	LoadMeshFile(m_cszCubeMeshFile, m_pCubeMesh);
	LoadMeshFile(m_cszBoardMeshFile, m_pBoardMesh);
	if(NULL == m_pCubeMesh || NULL == m_pBoardMesh)
		return false;

	// load textures
	for(int i = 0; i < g_numTextures; i++)
	{
		D3DXCreateTextureFromFile(m_pD3DDevice, m_cszTextureFileName[i], &m_pTextureLibrary[i]);
		if(NULL == m_pTextureLibrary[i])
			return false;
	}
	m_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);

	return true;
}

bool Graphics::SetupLight()		// setup a very basic D3DLIGHT9 light
{

	m_pD3DDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);
	m_pD3DDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(60, 60, 60));

	ZeroMemory(&m_DefaultLight, sizeof(D3DLIGHT9));

	m_DefaultLight.Type = D3DLIGHT_POINT;
	m_DefaultLight.Ambient = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	m_DefaultLight.Diffuse = D3DXCOLOR(0.1f, 0.1f, 0.1f, 1);		// color of the light
	m_DefaultLight.Specular = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1);
	m_DefaultLight.Position.x = 0;	
	m_DefaultLight.Position.y = 40;	
	m_DefaultLight.Position.z = 70;
	D3DXVec3Normalize((D3DXVECTOR3*)&m_DefaultLight.Direction, (D3DXVECTOR3*)&m_DefaultLight.Position);
	m_DefaultLight.Range = 1000.0f;
	m_DefaultLight.Attenuation0 = 0;
	m_DefaultLight.Attenuation1 = 0.05f;
	m_DefaultLight.Attenuation2 = 0.0f;

	m_DefaultLight2.Position.z = -60;

	m_pD3DDevice->SetLight(0, &m_DefaultLight);
	m_pD3DDevice->SetLight(1, &m_DefaultLight2);

	for(int i = 0; i < 3; i++)
		m_pD3DDevice->LightEnable(i, true);

	return true;
}

bool Graphics::SetupMaterials()
{
	ZeroMemory(&m_DefaultMaterial, sizeof(D3DMATERIAL9));
	ZeroMemory(&m_SelectionMaterial, sizeof(D3DMATERIAL9));
	m_DefaultMaterial.Ambient = D3DXCOLOR(1, 1, 1, 1);
	m_DefaultMaterial.Diffuse = D3DXCOLOR(0, 0, 0, 1);
	m_DefaultMaterial.Specular = D3DXCOLOR(0.1f, 0.1f, 0.1f, 1);
	m_DefaultMaterial.Power = 10;

	m_SelectionMaterial = m_DefaultMaterial;
	m_SelectionMaterial.Diffuse = D3DXCOLOR(1, 1, 1, 1);
	m_SelectionMaterial.Specular = D3DXCOLOR(1, 1, 1, 1);
	m_SelectionMaterial.Emissive = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1);
	
	m_pD3DDevice->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL);

	return true;
}


void Graphics::setPiece(bool** shape, int index, int size)
{
	Vec3D tempPos;

	m_PieceList[index].clear();
	for(int z = 0; z < size; ++z)
	{
		for(int x = 0; x < size; ++x)
		{
			if(shape[z][x])
			{ 
				// create the blocks based on the internal shape
				tempPos.x = (float)(x * 10);
				tempPos.z = (float)(z * -10);

				m_PieceList[index].push(tempPos);	// push the position into the list for that piece
			}
		}
	}
}



// a subfunction of Render()
void Graphics::RenderFont(TextBox* toRender)
{
	std::string tempStr = toRender->getText();
	RECT tempRect = toRender->getRect();

	m_pD3DFont->DrawTextA(NULL, tempStr.c_str(),
							tempStr.size(), &tempRect,
							DT_LEFT | DT_NOCLIP, D3DCOLOR_ARGB(255, 255, 255, 255));

}

void Graphics::MoveCameraTo(D3DXVECTOR3 newPos)
{
	m_Camera.eyePos = newPos;
	D3DXMatrixLookAtLH(&m_matView, &m_Camera.eyePos, &m_Camera.lookAt, &m_Camera.upVec);
	m_pD3DDevice->SetTransform(D3DTS_VIEW, &m_matView);
}

// to be called every game loop, clearing the back buffer, and drawing the next frame
bool Graphics::Render(Vector<Drawable*> vDrawables, Vector<TextBox*> vTextBoxes)
{
	if(!m_bInitialized)
	{
		MessageBox(0, "Graphics is being used without being initialized, shutting down...", 0, 0);
		return false;
	}

	// clear the entire back buffer and fill the screen to be dark grey
	m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.0f), 1.0f, 0);
	
	m_pD3DDevice->BeginScene();							// begin setting scene

	for(int i = 0; i < vDrawables.sizeOf(); ++i)
	{
		// draw all items, except the camera
		if(vDrawables[i]->m_iType != CAMERA)
		{
			m_pD3DDevice->SetTexture(0, m_pTextureLibrary[vDrawables[i]->m_iTextureIndex]);

			// apply a brighter material to the object if it has been selected
			if(vDrawables[i]->m_bSelected)
				m_pD3DDevice->SetMaterial(&m_SelectionMaterial);
			else
				m_pD3DDevice->SetMaterial(&m_DefaultMaterial);

			// prepare the world transform a little differently if it's a piece object
			if(vDrawables[i]->m_iType >= MON && vDrawables[i]->m_iType <= PENT11)
			{
				float x, z;
				int index = vDrawables[i]->m_iType;
				if(vDrawables[i]->owner == PLAYER2)
					index += (int)(g_numPieces * 0.5f);

				// draw each block of the piece
				for(int j=0; j < m_PieceList[index].sizeOf(); ++j)
				{
					x = vDrawables[i]->getPos().x;
					z = vDrawables[i]->getPos().z;

					x += m_PieceList[index][j].x;
					z += m_PieceList[index][j].z;
					D3DXMatrixTranslation(&m_matWorld, x, 0, z);
					m_pD3DDevice->SetTransform(D3DTS_WORLD, &m_matWorld);
					m_pCubeMesh->DrawSubset(0);
				}
			}
			else	// for non-piece objects, prepare world transform for an object
			{
				vDrawables[i]->CreateWorldTransform(m_matWorld);
				m_pD3DDevice->SetTransform(D3DTS_WORLD, &m_matWorld);
				m_pBoardMesh->DrawSubset(0);
			}
		}
		else
		{
			// convert position Vec3D into D3DXVECTOR3
			D3DXVECTOR3 temp;
			temp.x = vDrawables[i]->getPos().x;
			temp.y = vDrawables[i]->getPos().y;
			temp.z = vDrawables[i]->getPos().z;
			MoveCameraTo(temp);
		}
	}

	// render fonts
	for(int i = 0; i < vTextBoxes.sizeOf(); ++i)
	{
		RenderFont(vTextBoxes[i]);
	}

	// render sprites

	m_pD3DDevice->EndScene();							// stop setting the scene


	m_pD3DDevice->Present(NULL, NULL, NULL, NULL);		// present the completed frame to the screen

	vDrawables.release();									// release the memory now that we don't need it anymore

	return true;
}

void Graphics::LoadMeshFile(const char* cszXFile, ID3DXMesh*& pMesh)
{
	pMesh = NULL;

	/* Prepare buffers returned by D3DXLoadMeshFromX */
	ID3DXBuffer* pAdjacency = NULL;			// required for OptimizeInPlace
	ID3DXBuffer* pMaterials = NULL;			// materials for each subset
	ID3DXBuffer* pEffectsInstances = NULL;	// not interested in effects now
	DWORD dwNumMaterials = 0;

	/* Load the file into the mesh */
	HR(D3DXLoadMeshFromX(cszXFile, D3DXMESH_MANAGED, m_pD3DDevice, &pAdjacency,
							&pMaterials, &pEffectsInstances, &dwNumMaterials, &pMesh));
	DWORD dError = ::GetLastError();

	/* What is the flexible vertex format of the vertex buffer before cloning? */
	DWORD dMeshFVF = pMesh->GetFVF();

	/* Clone the mesh, ensuring that it has room for position, normals, and texture coordinates */
	ID3DXMesh* pClone = NULL;
	HR(pMesh->CloneMeshFVF(D3DPOOL_MANAGED, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2, m_pD3DDevice, &pClone));
	pMesh->Release();
	pMesh = pClone;

	/* Recompute the normals if they were not in the file */
	if (0 == (dMeshFVF & D3DFVF_NORMAL))
	{
		HR(D3DXComputeNormals(pMesh, (DWORD*)pAdjacency->GetBufferPointer()));
	}

	/* Optimize the mesh */
	HR(pMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
		(DWORD*)pAdjacency->GetBufferPointer(),
		0, 0, 0));

	/* Done with buffers */
	pAdjacency->Release();
	pEffectsInstances->Release();
	pMaterials->Release();
}


Graphics::~Graphics()
{
	if(NULL != m_pD3DDevice)		// if the device was set up properly
		m_pD3DDevice->Release();
}